# -*- coding: utf-8 -*-
import sys
if sys.getdefaultencoding() != 'utf-8':
    reload(sys)
    sys.setdefaultencoding('utf-8')
import logging, re, os, time, math
import xbmcswift2
from xbmcswift2 import *
from pprint import pprint
from traceback import print_exc
import urllib, urllib2
from xbmc_utils import RequestFromURLWithOption, UserAgent_IE

plugin = Plugin()

dbg = True

STRINGS = {}

log = plugin.log

class StreamToLogger(object):
    """
    Fake file-like stream object that redirects writes to a logger instance.
    """
    def __init__(self, logger, name, log_level=logging.INFO):
        self.logger = logger
        self.name = name
        self.log_level = log_level
        self.linebuf = ''
        self.dummy = False;
 
    def write(self, buf):
        if self.dummy:
            return
        for line in buf.rstrip().split("\n"):
            self.logger.log(self.log_level, '%s: %s' % (self.name, line.rstrip()))
 
#logging.basicConfig(
#   level=logging.DEBUG,
#   format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
#   filename="out.log",
#   filemode='a'
#)
 
if xbmcswift2.CLI_MODE:
#    plog = sys.stdout
    pass
else:
    slout = StreamToLogger(log, 'STDOUT', logging.DEBUG)
#    plog = sl
    ori_stdout = sys.stdout
    sys.stdout = slout
    slerr = StreamToLogger(log, 'STDERR', logging.ERROR)
    sys.stderr = slerr
 
def patch_the_plugin():
    import inspect
    def set_resolved_url(self, url=None, mimetype=None):
        if type(url) in [str, unicode]:
            item = xbmcswift2.ListItem(path=url)
        elif type(url) == dict:
            item = xbmcswift2.ListItem.from_dict(**url)
        else:
            item = xbmcswift2.ListItem(url)
        if mimetype:
            item.set_property('mimetype', mimetype)
        item.set_played(True)
        if not self._end_of_directory:
            xbmcplugin.setResolvedUrl(self.handle, url is not None, item.as_xbmc_listitem())
            # prevent from auto call endOfDirectory, which will not work because the handle already be erased by xbmc
            self._end_of_directory = True
            return [item]
        assert False, 'Already called endOfDirectory or setResolvedUrl.'
    Plugin.set_resolved_url = set_resolved_url

    def notify(self, msg='', title=None, delay=5000, image=''):
        if title:
            self.log.info('[NOTIFY] %s: %s' % (title, msg))
        else:
            self.log.info('[NOTIFY] %s' % msg)
        self._notify(msg, title, delay, image)
    Plugin._notify = Plugin.notify
    Plugin.notify = notify

    if len(inspect.getargspec(Plugin.keyboard).args) == 4:
        def keyboard(self, default=None, heading=None, hidden=False, prefered_lang='en'):
            if heading is None:
                heading = self.addon.getAddonInfo('name')
            if default is None:
                default = ''
            keyboard = None
            if not hidden and prefered_lang.lower() in ['cn', 'zh-cn']:
                try:
                    import ChineseKeyboard
                    keyboard = ChineseKeyboard.Keyboard(default, heading)
                    keyboard.doModal()
                except:
                    pass
            if not keyboard:
                keyboard = xbmc.Keyboard(default, heading, hidden)
                keyboard.doModal()
            if keyboard.isConfirmed():
                return keyboard.getText()
        Plugin.keyboard = keyboard

    from xbmcswift2.urls import NotFoundException
    def _dispatch(self, path):
        for rule in self._routes:
            try:
                view_func, items = rule.match(path)
            except NotFoundException:
                continue
            log.info('Request for "%s" matches rule for function "%s"',
                     path, view_func.__name__)
            self.cur_func_name = view_func.__name__
            listitems = view_func(**items)
            self.cur_func_name = None

            # TODO: Verify the main UI handle is always 0, this check exists so
            #       we don't erroneously call endOfDirectory for alternate
            #       threads
            # Allow the returning of bare dictionaries so we can cache view
            if not self._end_of_directory and self.handle >= 0:
                if listitems is None:
                    listitems = self.finish(succeeded=False)
                else:
                    listitems = self.finish(listitems)

            return listitems
        raise NotFoundException, 'No matching view found for %s' % path
    Plugin._dispatch = _dispatch

    def get_storage_path(self, name='main'):
        return os.path.join(self.storage_path, name)
    Plugin.get_storage_path = get_storage_path

    def get_setting(self, key, converter, fallback=None):
        try:
            return self.get_setting2(key, converter)
        except:
            if fallback is not None:
                return fallback
            raise
    if not hasattr(Plugin, 'get_setting2'):
        Plugin.get_setting2 = Plugin.get_setting
        Plugin.get_setting = get_setting

    from xbmcswift2.urls import UrlRule
    def __init__(self, url_rule, view_func, name, options):
        self._name = name
        self._url_rule = url_rule
        self._view_func = view_func
        self._options = options or {}
        self._keywords = re.findall(r'\<(.+?)\>', url_rule)
        
        #change <> to {} for use with str.format()
        self._url_format = self._url_rule.replace('<', '{').replace('>', '}')
        
        # Make a regex pattern for matching incoming URLs
        p = self._url_rule.replace('<', '(?P<').replace('>', '>[^/]*?)')
        if p[-1] == '/':
            # the tailing '/' sometimes removed by the xbmc, we should be able
            # to match the stripped url anyway.
            p += '?'
        
        try:
            self._regex = re.compile('^' + p + '$')
        except re.error, e:
            raise ValueError, ('There was a problem creating this URL rule. '
                               'Ensure you do not have any unpaired angle '
                               'brackets: "<" or ">"')
    UrlRule.__init__ = __init__

    def add_items(self, items):
        # For each item if it is not already a list item, we need to create one
        _items = []
        info_type = self.info_type if hasattr(self, 'info_type') else 'video'

        # Create ListItems for anything that is not already an instance of
        # ListItem
        for item in items:
            if not isinstance(item, xbmcswift2.ListItem):
                if 'info_type' not in item.keys():
                    item['info_type'] = info_type
                item = xbmcswift2.ListItem.from_dict(**item)
            _items.append(item)
            p, it, d = item.as_tuple()
            assert isinstance(p, basestring)
            xbmcplugin.addDirectoryItem(self.handle, p, it, d, len(items))

        # We need to keep track internally of added items so we can return them
        # all at the end for testing purposes
        self.added_items.extend(_items)

        # Possibly need an if statement if only for debug mode
        return _items
    Plugin.add_items = add_items

    @classmethod
    def from_dict(cls, label=None, label2=None, icon=None, thumbnail=None,
                  path=None, selected=None, info=None, properties=None,
                  context_menu=None, replace_context_menu=False,
                  is_playable=None, info_type='video', is_folder=None):
        '''A ListItem constructor for setting a lot of properties not
        available in the regular __init__ method. Useful to collect all
        the properties in a dict and then use the **dct to call this
        method.
        '''
        listitem = cls(label, label2, icon, thumbnail, path)

        if selected is not None:
            listitem.select(selected)

        if info:
            listitem.set_info(info_type, info)

        if is_playable:
            listitem.set_is_playable(True)
        elif is_folder is not None:
            listitem.is_folder = is_folder

        if properties:
            for key, val in properties:
                listitem.set_property(key, val)

        if context_menu:
            listitem.add_context_menu_items(context_menu, replace_context_menu)

        return listitem
    xbmcswift2.ListItem.from_dict = from_dict

    def open_url_in_sys_browser(self, url):
        os.system(str(r'uiopen "%s"' % url.split('|', 2)[0]))

    Plugin.open_url = open_url_in_sys_browser


patch_the_plugin()

@plugin.route('/open_url_in_web_viewer/<url>/')
def open_url_in_web_viewer(url):
    try:
        from webviewer import webviewer
    except:
        print_exc()
        plugin.notify(_('Please install XBMC WebViewer addon first.'))
        return False
    webviewer.getWebResult(url.split('|', 2)[0], dialog=True)
    return True
    
sto = None
def get_sto():
    global sto
    try:
        sto = plugin.get_storage()
    except:
        p = plugin.get_storage_path()
        if os.path.isfile(p):
            os.remove(p)
            log.info('corrupted storage file %s deleted.' % p)
            sto = plugin.get_storage()
        else:
            raise
    return sto

if xbmcswift2.CLI_MODE:
    pass
else:
    sys.stdout.dummy = True

def menu_item_goto(label, url, replace=False):
    return (
        label,
        'Container.Update(%s,%s)' % (str(url), str(replace)),
    )

def run_plugin_item(label, url):
    return (
        label,
        'RunPlugin(%s)' % str(url),
    )

def run_plugin(url):
    xbmc.executebuiltin('RunPlugin(%s)' % str(url))

def show_plugin_path(path, replace=False):
    log.info('show_plugin_path(%s, %s)' % (path, str(replace)))
    xbmc.executebuiltin('Container.Update(%s,%s)' % (str(path), str(replace)))

@plugin.route('/show_contextmenu')
def show_contextmenu():
#    ret = plugin.set_resolved_url()
#    time.sleep(0.5)
    xbmc.executebuiltin('Action(ContextMenu)')
#    return ret
#    return plugin.finish(succeeded=False)

def add_page_nav_items(items, pageNo, hasNextPage, pageURLFunc):
    if pageNo > 1:
        # have one prev page, add a nav link
        items.insert(0,
            {
                'label': '<%s>' % _('Prev Page'),
                'path': pageURLFunc(str(pageNo-1)),
            }
        )
#        items.insert(0, 
#            {
#                'label': '<%s>' % _('Page {pageNo}').format(pageNo=1),
#                'path': pageURLFunc('1'),
#            }
#        )
    if hasNextPage:
        # have one more page, add a nav link
        items.append(
            {
                'label': '<%s>' % _('Next Page'),
                'path': pageURLFunc(str(pageNo+1)),
            }
        )
#        items.append(
#            {
#                'label': '<%s>' % _('Page {pageNo}').format(pageNo=pageNum),
#                'path': pageURLFunc(str(pageNum)),
#            }
#        )
    return items

def get_page_nav_items(pageNo, pageNum, pageURLFunc):
    if pageNum <= 1:
        return []
    items = []
    pageNoHasLink = []
    if pageNo > 1:
        # have one prev page, add a nav link
        items.append(menu_item_goto(
#            '<%s>' % _('Page {pageNo}').format(pageNo=1),
            '<%s>' % _('First Page'),
            pageURLFunc('1')
        ))
        pageNoHasLink.append(1)
    if pageNo > 2:
        items.append(menu_item_goto(
#            '<%s>' % _('Page {pageNo}').format(pageNo=pageNo-1),
            '<%s>' % _('Prev Page'),
            pageURLFunc(str(pageNo-1)),
        ))
        pageNoHasLink.append(pageNo-1)
    middlePos = len(items)
    if pageNo + 1 < pageNum:
        # have one more page, add a nav link
        items.append(menu_item_goto(
#            '<%s>' % _('Page {pageNo}').format(pageNo=pageNo+1),
            '<%s>' % _('Next Page'),
            pageURLFunc(str(pageNo+1)),
        ))
        pageNoHasLink.append(pageNo+1)
    if pageNo < pageNum:
        items.append(menu_item_goto(
#            '<%s>' % _('Page {pageNo}').format(pageNo=pageNum),
            '<%s>' % _('Final Page'),
            pageURLFunc(str(pageNum)),
        ))
        pageNoHasLink.append(pageNum)
    if len(pageNoHasLink) + 1 < pageNum:
        # at least one page has no direct link, maybe we need to add them to context menu items
#        items.insert(middlePos, menu_item_goto(
        items.append(menu_item_goto(
            '<%s>' % _('Jump To...'),
            plugin.url_for('select_page', url=pageURLFunc('<pageNo>'), page=str(pageNo), pagenum=str(pageNum)),
        ))
    navItem = {
        'label': '<%s>' % _('Current: {pageNo}/{pageNum}').format(pageNo=pageNo, pageNum=pageNum),
        'path': plugin.url_for('show_contextmenu'),
        'replace_context_menu': True,
        'context_menu': items,
    }
    if pageNo < pageNum:
        navItem['label'] = '<%s> %s' % (_('Next Page'), navItem['label'])
        navItem['path'] = pageURLFunc(str(pageNo+1))
    else:
        navItem['is_folder'] = False
        navItem['info'] = { 'title': navItem['label'] }
    pprint(navItem)
    return [navItem]

@plugin.route('/select_page/<url>/<pagenum>/<page>/')
def select_page(url, page, pagenum):
    log.info('select_page(%s, %s, %s)' % (repr(url), page, pagenum))
    pageNo = int(page)
    pageNum = int(pagenum)
    page_list = [_('Page {pageNo}').format(pageNo=i) for i in xrange(1, pageNum+1)]
    page_list[pageNo-1] = '[COLOR FFFF0000]' + page_list[pageNo-1] + '[/COLOR]'
    dialog = xbmcgui.Dialog()
    index = dialog.select(_('Jump to page'), page_list)
    if index < 0 or index == pageNo - 1:
        return
    url = url.replace(urllib.quote_plus('<pageNo>'), str(index+1))
    log.info('user select page %s: %s' % (index+1, url))
    return plugin.redirect(str(url))

def add_nav_item(items, navItem, insertPos=0):
    assert type(navItem) == dict
    navItem['is_folder'] = False
    navItem['info'] = { 'title': navItem['label'] }
    navItem['path'] = plugin.url_for('show_contextmenu')
    navItem['replace_context_menu'] = True
    context_menu_items = navItem.get('context_menu')
#    if context_menu_items:
#        del navItem['context_menu']
#    navItem = xbmcswift2.ListItem.from_dict(**navItem)
    if context_menu_items:
        context_menu_items.append(menu_show_settings)
        if xbmcswift2.CLI_MODE:
            items[insertPos:insertPos] = items_from_context_menu(context_menu_items)
#        navItem.add_context_menu_items(context_menu_items, True)
    items.insert(insertPos, navItem)

def ask_and_open_settings(q1, q2='', q3=''):
    dialog = xbmcgui.Dialog()
    yes = dialog.yesno(plugin.name, q1, q2, q3)
    if yes:
        show_settings()
    return yes

class MySimpleDownloader(object):
    class StopDownloading(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)

    def _pbhook(self, numblocks, blocksize, filesize, dp, start_time):
        try:
            percent = min(numblocks * blocksize * 100 / filesize, 100)
            currently_downloaded = float(numblocks) * blocksize / (1024 * 1024)
            kbps_speed = numblocks * blocksize / (time.time() - start_time)
            if kbps_speed > 0:
                eta = (filesize - numblocks * blocksize) / kbps_speed
            else:
                eta = 0
            kbps_speed = kbps_speed / 1024
            total = float(filesize) / (1024 * 1024)
            # print (
                # percent,
                # numblocks,
                # blocksize,
                # filesize,
                # currently_downloaded,
                # kbps_speed,
                # eta,
                # )
            mbs = '%.02f MB of %.02f MB' % (currently_downloaded, total)
            e = 'Speed: %.02f Kb/s ' % kbps_speed
            e += 'ETA: %02d:%02d' % divmod(eta, 60)
            dp.update(percent, mbs, e)
            #print percent, mbs, e
        except:
            percent = 100
            dp.update(percent)
        if dp.iscanceled():
            dp.close()
            raise MySimpleDownloader.StopDownloading('Stopped Downloading') 

    def save_to_file(self, response, dest, report_hook=None, chunk_size=8192):
        total_size = response.info().getheader('Content-Length').strip()
        total_size = int(total_size)
        bytes_so_far = 0
 
        try:
            import xbmcvfs
            fp = xbmcvfs.File(dest, 'w')
        except:
            fp = open(dest, 'w')
        try:
            while True:
                chunk = response.read(chunk_size)
                bytes_so_far += len(chunk)
 
                fp.write(chunk)
 
                if not chunk:
                    break
 
                if report_hook:
                    report_hook(bytes_so_far/chunk_size, chunk_size, total_size)
        finally:
            fp.close()
 
        return bytes_so_far
 
    def download(self, name, opts):
        nogui = opts.get('nogui')
        if not nogui:
            dp = xbmcgui.DialogProgress()
            dp.create('Downloading', '', name)
        start_time = time.time()
        url = opts['url']
        dest = opts['download_path']
        if dest[-1] in ['/', "\\"]:
            dest += name
        log.info(u'download [%s] -> [%s]' % (url, dest))
        try:
            req = RequestFromURLWithOption(url)
            if req.get_header('User-agent') is None:
                req.add_header('User-Agent', UserAgent_IE)
            fp = urllib2.urlopen(req)
            try:
                headers = fp.info()
                lastModified = headers['Last-Modified']
                lm = None
                if lastModified:
                    try:
                        lm = time.mktime(time.strptime(lastModified, '%a, %d %b %Y %H:%M:%S GMT'))-time.timezone
                        if os.path.exists(dest):
                            mtime = os.path.getmtime(dest)
                            print 'local [%s] remote [%s]' % (time.ctime(mtime), time.ctime(lm))
                    except:
                        print_exc()
                else:
                    print 'got no last-modified header info'
                try:
                    if not nogui:
                        def report_hook(nb, bs, fs):
                            MySimpleDownloader._pbhook(self, nb, bs, fs, dp, start_time)
                    else:
                        report_hook = None
                    self.save_to_file(fp, dest, report_hook)
                    if lm:
                        try:
                            os.utime(dest, (lm, lm))
                        except OSError, e:
                            log.warning('can not set last-modified time [%s]: %s' % (lastModified, e))
                            pass
                    if not nogui:
                        plugin.notify(u'File [%s] downloaded to [%s]' % (name, dest))
                except:
                    if os.path.exists(dest):
                        try:
                            os.remove(dest)
                        except:
                            print_exc()
                    raise
            finally:
                fp.close()
        except urllib2.HTTPError, e:
            if e.code==304:
                return 'not modified'
            raise
        except MySimpleDownloader.StopDownloading:
            return 'canceled'
        except urllib.ContentTooShortError:
            return 'false'
        except OSError, e:
            print_exc()
            log.error('download error: %s' % e)
            return 'false'
        return 'downloaded'


def get_simpledownloader():
    from SimpleDownloader import SimpleDownloader
    def _downloadURL(self, filename, item):
        self.common.log(filename)

        req = RequestFromURLWithOption(item['url'])
        if req.get_header('User-agent') is None:
            req.add_header('User-Agent', UserAgent_IE)

        try:
            file = self.common.openFile(item["path_incomplete"], "wb")
            con = urllib2.urlopen(req)

            item["total_size"] = 0
            chunk_size = 1024 * 32

            if con.info().getheader("Content-Length").strip():
                item["total_size"] = int(con.info().getheader("Content-Length").strip())

            params = {"bytes_so_far": 0, "mark": 0.0, "queue_mark": 0.0, "obytes_so_far": 0}
            item["percent"] = 0.1
            item["old_percent"] = -1

            while "quit" not in params:
                chunk = con.read(chunk_size)
                file.write(chunk)
                params["bytes_so_far"] += len(chunk)

                if params["mark"] == 0.0 and params["bytes_so_far"] > 0:
                    params["mark"] = time.time()
                    self.common.log("Mark set")

                self._generatePercent(item, params)

                self.common.log("recieved chunk: %s - %s" % ( repr(item["percent"] > item["old_percent"]), repr(time.time() - params["queue_mark"])), 4)
                if item["percent"] > item["old_percent"] or time.time() - params["queue_mark"] > 30:
                    self._run_async(self._updateProgress(filename, item, params))

                    item["old_percent"] = item["percent"]

                params["obytes_so_far"] = params["bytes_so_far"]

                if not chunk:
                    break
            self.common.log("Loop done")

            con.close()
            file.close()
        except:
            print_exc()
            self.common.log("Download failed.")
            try:
                con.close()
            except:
                self.common.log("Failed to close download stream")

            try:
                file.close()
            except:
                self.common.log("Failed to close file handle")

            self._showMessage(self.language(204), "ERROR")
            return 500

        if "quit" in params:
            self.common.log("Download aborted.")
            return 300

        self.common.log("Done")
        return 200

#    SimpleDownloader._old_downloadURL = SimpleDownloader._downloadURL
    SimpleDownloader._downloadURL = _downloadURL
    downloader = SimpleDownloader()
    return downloader

def get_downloader(use_simpledownloader=False):
    if use_simpledownloader:
        downloader = get_simpledownloader()
    else:
        downloader = MySimpleDownloader()
    downloader.dbg = dbg
    return downloader

@plugin.route('/open_url/<url>/')
def open_url(url):
    log.info('open_url(%s)' % url)
    plugin.open_url(url)

@plugin.route('/settings')
def show_settings():
    log.info('show_settings()')
    plugin.open_settings()

@plugin.route('/clear_info_cache')
def clear_info_cache():
    log.info('clear_info_cache()')
    plugin.notify(_('Cleaning info cache...'), delay=60000, image=addon_icon_path)
    get_sto()
    sto.clear()
    sto.sync()
    log.info('clear_info_cache() all cached info cleared.')
    plugin.notify(_('Info cache cleared.'), image=addon_icon_path)
    
def expandItemsFromContextMenu(items):
    return items
    if xbmcswift2.CLI_MODE:
        for i in range(len(items), 0, -1):
            item = items[i-1]
            if item.get('context_menu'):
                items[i:i] = items_from_context_menu(item['context_menu'])
    return items
    
def items_from_context_menu(context_menu_items):
    items = []    
    CONTAINER_UPDATE_RE = re.compile('^Container.Update\((.*),(False|True)\)$')
    for title, action in context_menu_items:
        m = CONTAINER_UPDATE_RE.match(action)
        if not m:
            continue
        items.append({
            'label': title,
            'path': m.group(1),
        })
    return items

@plugin.route('/show/<type>/<dir_url>/play/<play_url>/')
def show_and_play(type, dir_url, play_url):
    log.info('show_and_play(%s, %s, %s)' % (type, dir_url, play_url))
    # used for switch to specified media win (video/music/picture) listing and play.
    xbmc.executebuiltin('ActivateWindow(%s,%s)' % (type, dir_url))

def message(msg):
    dialog = xbmcgui.Dialog()
    dialog.ok(plugin.name, str(msg))

@plugin.route('/show_plugin_dir/<url>/<replace>/', options = {'replace': '0'})
def show_plugin_dir(url, replace='0'):
    ret = plugin.set_resolved_url()
    show_plugin_path(url, replace=='1')
    return ret

def _(string_id):
    if type(string_id) == int:
        return plugin.get_string(string_id)
    elif string_id in STRINGS:
        return plugin.get_string(STRINGS[string_id])
    else:
        log.warning('String is missing: %s' % string_id)
        return string_id

menu_reload_page = (
    _('Reload Page'),
    'Container.Refresh',
)
menu_show_settings = (
    _('Addon Preference'),
    'RunPlugin(%s)' % plugin.url_for('show_settings'),
)

